<?php
// +----------------------------------------------------------------------
// | WebSocketServer 类
// +----------------------------------------------------------------------
// | 提供WebSocket服务端的启动、停止、重启和消息处理功能
// +----------------------------------------------------------------------

namespace fast;

use Workerman\Worker;
use Workerman\Connection\TcpConnection;

class WebSocketServer
{
    // 单例实例
    private static $instance = null;
    // WebSocket服务实例
    private $worker = null;
    // 客户端连接映射，格式为 client_id => connection
    private $clients = [];
    // 进程ID文件路径
    private $pidFile = null;
    // 日志文件路径
    private $logFile = null;
    // 服务配置
    private $config = [];
    
    /**
     * 构造函数
     * @param array $config 配置参数
     */
    private function __construct($config = [])
    {
        $this->config = array_merge([
            'host' => '0.0.0.0',
            'port' => 8282,
            'name' => 'WebSocketServer',
            'count' => 4,
            'daemon' => false,
        ], $config);
        
        // 设置PID文件和日志文件路径
        $runtimePath = ROOT_PATH . 'runtime/';
        $this->pidFile = $runtimePath . 'websocket.pid';
        $this->logFile = $runtimePath . 'websocket.log';
        
        // 确保目录存在
        if (!is_dir($runtimePath)) {
            mkdir($runtimePath, 0755, true);
        }
    }
    
    /**
     * 获取单例实例
     * @param array $config 配置参数
     * @return WebSocketServer
     */
    public static function getInstance($config = [])
    {
        if (self::$instance === null) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }
    
    /**
     * 初始化WebSocket服务
     */
    public function init()
    {
        // 创建WebSocket服务
        $this->worker = new Worker('websocket://' . $this->config['host'] . ':' . $this->config['port']);
        
        // 设置服务名称
        $this->worker->name = $this->config['name'];
        
        // 设置进程数
        $this->worker->count = $this->config['count'];
        
        // 设置守护进程模式
        Worker::$daemonize = $this->config['daemon'];
        
        // 设置PID文件
        Worker::$pidFile = $this->pidFile;
        
        // 设置日志文件
        Worker::$logFile = $this->logFile;
        
        // 注册回调函数
        $this->worker->onWorkerStart = [$this, 'onWorkerStart'];
        $this->worker->onConnect = [$this, 'onConnect'];
        $this->worker->onMessage = [$this, 'onMessage'];
        $this->worker->onClose = [$this, 'onClose'];
        $this->worker->onError = [$this, 'onError'];
        
        return $this;
    }
    
    /**
     * 启动服务
     */
    public function start()
    {
        if (!$this->worker) {
            $this->init();
        }
        
        $this->log('WebSocket服务启动中...');
        Worker::runAll();
    }
    
    /**
     * 停止服务
     */
    public function stop()
    {
        $this->log('WebSocket服务停止中...');
        Worker::stopAll();
    }
    
    /**
     * 重启服务
     */
    public function restart()
    {
        $this->log('WebSocket服务重启中...');
        Worker::reloadAllWorkers();
    }
    
    /**
     * 服务启动时的回调
     * @param Worker $worker
     */
    public function onWorkerStart(Worker $worker)
    {
        $this->log('进程 ' . $worker->id . ' 启动成功');
    }
    
    /**
     * 连接建立时的回调
     * @param TcpConnection $connection
     */
    public function onConnect(TcpConnection $connection)
    {
        // 保存客户端连接
        $this->clients[$connection->id] = $connection;
        
        $this->log('客户端连接: ' . $connection->id . ' 来自 ' . $connection->getRemoteIp() . ':' . $connection->getRemotePort());
        
        // 发送连接成功消息
        $this->sendToClient($connection->id, ['type' => 'connect', 'message' => '连接成功', 'client_id' => $connection->id]);
        
        // 广播新客户端连接消息
        $this->broadcast([
            'type' => 'system',
            'message' => '新客户端加入',
            'client_id' => $connection->id,
            'total_clients' => count($this->clients)
        ], [$connection->id]); // 排除自己
    }
    
    /**
     * 接收消息时的回调
     * @param TcpConnection $connection
     * @param string $message
     */
    public function onMessage(TcpConnection $connection, $message)
    {
        $this->log('收到客户端 ' . $connection->id . ' 的消息: ' . $message);
        
        try {
            // 解析消息
            $data = json_decode($message, true);
            
            if (empty($data)) {
                throw new \Exception('消息格式错误');
            }
            
            // 根据消息类型处理
            switch ($data['type'] ?? 'message') {
                case 'broadcast':
                    // 广播消息
                    $this->broadcast([
                        'type' => 'message',
                        'from' => $connection->id,
                        'message' => $data['message'],
                        'time' => date('Y-m-d H:i:s')
                    ]);
                    break;
                    
                case 'private':
                    // 私聊消息
                    if (empty($data['to'])) {
                        throw new \Exception('缺少目标客户端ID');
                    }
                    
                    $this->sendToClient($connection->id, [
                        'type' => 'private',
                        'from' => $connection->id,
                        'to' => $data['to'],
                        'message' => $data['message'],
                        'time' => date('Y-m-d H:i:s')
                    ]);
                    
                    $this->sendToClient($data['to'], [
                        'type' => 'private',
                        'from' => $connection->id,
                        'to' => $data['to'],
                        'message' => $data['message'],
                        'time' => date('Y-m-d H:i:s')
                    ]);
                    break;
                    
                case 'ping':
                    // 心跳消息
                    $this->sendToClient($connection->id, ['type' => 'pong', 'time' => date('Y-m-d H:i:s')]);
                    break;
                    
                default:
                    // 默认作为普通消息处理
                    $this->sendToClient($connection->id, [
                        'type' => 'response',
                        'message' => '已收到消息',
                        'data' => $data,
                        'time' => date('Y-m-d H:i:s')
                    ]);
                    break;
            }
        } catch (\Exception $e) {
            // 发送错误消息
            $this->sendToClient($connection->id, [
                'type' => 'error',
                'message' => $e->getMessage(),
                'time' => date('Y-m-d H:i:s')
            ]);
        }
    }
    
    /**
     * 连接关闭时的回调
     * @param TcpConnection $connection
     */
    public function onClose(TcpConnection $connection)
    {
        // 从客户端列表中移除
        if (isset($this->clients[$connection->id])) {
            unset($this->clients[$connection->id]);
        }
        
        $this->log('客户端断开连接: ' . $connection->id);
        
        // 广播客户端离开消息
        $this->broadcast([
            'type' => 'system',
            'message' => '客户端离开',
            'client_id' => $connection->id,
            'total_clients' => count($this->clients)
        ]);
    }
    
    /**
     * 错误处理回调
     * @param TcpConnection $connection
     * @param int $code
     * @param string $msg
     */
    public function onError(TcpConnection $connection, $code, $msg)
    {
        $this->log('错误: [' . $code . '] ' . $msg, 'error');
    }
    
    /**
     * 发送消息给指定客户端
     * @param string $clientId 客户端ID
     * @param mixed $data 消息数据
     * @return bool
     */
    public function sendToClient($clientId, $data)
    {
        if (!isset($this->clients[$clientId])) {
            $this->log('客户端 ' . $clientId . ' 不存在', 'error');
            return false;
        }
        
        try {
            $connection = $this->clients[$clientId];
            $connection->send(json_encode($data, JSON_UNESCAPED_UNICODE));
            return true;
        } catch (\Exception $e) {
            $this->log('发送消息给客户端 ' . $clientId . ' 失败: ' . $e->getMessage(), 'error');
            return false;
        }
    }
    
    /**
     * 广播消息给所有客户端
     * @param mixed $data 消息数据
     * @param array $exclude 排除的客户端ID列表
     */
    public function broadcast($data, $exclude = [])
    {
        $message = json_encode($data, JSON_UNESCAPED_UNICODE);
        
        foreach ($this->clients as $clientId => $connection) {
            // 排除指定的客户端
            if (!empty($exclude) && in_array($clientId, $exclude)) {
                continue;
            }
            
            try {
                $connection->send($message);
            } catch (\Exception $e) {
                $this->log('广播消息给客户端 ' . $clientId . ' 失败: ' . $e->getMessage(), 'error');
            }
        }
    }
    
    /**
     * 记录日志
     * @param string $message 日志消息
     * @param string $level 日志级别
     */
    private function log($message, $level = 'info')
    {
        $timestamp = date('Y-m-d H:i:s');
        $log = "[$timestamp] [$level] $message\n";
        
        // 输出到控制台
        echo $log;
        
        // 写入日志文件
        file_put_contents($this->logFile, $log, FILE_APPEND);
    }
    
    /**
     * 获取当前连接数
     * @return int
     */
    public function getClientCount()
    {
        return count($this->clients);
    }
    
    /**
     * 获取所有客户端ID
     * @return array
     */
    public function getClientIds()
    {
        return array_keys($this->clients);
    }
}